
#include <dlfcn.h>
#include <sys/stat.h>
#include "internal.h"
#include "potion.h"
#include "khash.h"
#include "table.h"

#define MODS P->modules

// 命名 potion_DYLIB_init
// TODO hsq 当前 module == lib ，而非 lib 包含 module
typedef PN (*DYLIB_INIT_F)(Potion *);

PN dylib_error(Potion *P, const char *what, const char *filename) {
    const char *msg = dlerror();
    if (msg) {
      return PN_STRING(msg);
    } else {
      size_t len = strlen(filename);
      char msg[len + 32];
      snprintf(msg, len + 32, "failed to %s %s", what, filename);
      return PN_STRING(msg);
    }
}

// TODO hsq handle: NULL RTLD_NEXT RTLD_SELF RTLD_MAIN_ONLY RTLD_DEFAULT
#define FIND_INIT_F(handle) \
  size_t len = strlen(module); \
  char init_f_name[len + 16]; \
  snprintf(init_f_name, len + 16, "potion_%s_init", module); \
  DYLIB_INIT_F init_f = dlsym(handle, init_f_name)

#ifndef DYLIB
// 检查、调用内置模块，返回 PNP融通 | Error | nil 。
PN check_builtin(Potion *P, const char *module) {
  FIND_INIT_F(RTLD_SELF);
  if (init_f) {
    PN ret = init_f(P);
    return PN_TEST(ret) ? ret : PN_TRUE;
  }
  return PN_NIL;
}
#endif

// 加载共享库模块，返回 PNP融通 | Error | nil 。
PN load_dylib(Potion *P, const char *module, const char *filename) {
  struct stat stats;
  if (stat(filename, &stats) == -1) {
    return PN_NIL;
  }

  // TODO hsq RTLD_LAZY, RTLD_NOW | (seeglb ? RTLD_GLOBAL : RTLD_LOCAL)
  void *handle = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
  if (!handle) {
    return potion_error(P, dylib_error(P, "OPEN-DYLIB", filename), 0, 0, PN_NIL);
  }

  FIND_INIT_F(handle);
  if (!init_f) {
    return potion_error(P, dylib_error(P, "INIT-DYLIB", filename), 0, 0, PN_NIL);
  }

  // TODO hsq 在被 GC 时需要调用 clclose 。
  // if (dlclose(handle)) {
  //   return potion_error(P, dylib_error(P, "CLOSE-DYLIB", filename), 0, 0, PN_NIL);
  // }

  if (init_f) {
    PN ret = init_f(P);
    return PN_TEST(ret) ? ret : PN_TRUE;
  }
  return PN_NIL;
}

// 加载字节码或加载代码并编译，返回 PNProto | Error | nil 。
PN load_or_compile(Potion *P, const char *filename) {
  PN buf = read_file(P, filename);
  if (buf == PN_NIL || PN_IS_ERROR(buf)) return buf;

  // 加载字节码
  PN code = potion_source_load(P, buf);
  // 不是字节码，则解析源码，打印 AST ，编译得到字节码
  if (!PN_IS_PROTO(code)) {
    code = potion_parse(P, buf);
    if (PN_IS_ERROR(code)) {
      // STR_AND_PRINT(code);
      // return PN_NIL;
      return code;
    }
    code = potion_send(code, $(compile), PN_STRING(filename), PN_NIL);
  }

  return code;
}

// 加载模块，先 .pnb 文件再 .pn 文件， false | Error 为失败。
static PN load_module(Potion *P, const char *module, size_t len) {
  char filename[len + 8];
  PN code;

  #ifndef DYLIB
    code = check_builtin(P, module);
    if (PN_IS_ERROR(code)) {
      return code;
    }
    if (code != PN_NIL) {
      return code;
    }
  #endif

  // memmove(filename, module, len);
  // memmove(filename + len, PN_EXT_BYTECODE, sizeof(PN_EXT_BYTECODE));
  snprintf(filename, len + 8, "%s" PN_EXT_BYTECODE, module);
  code = load_or_compile(P, filename);
  if (code == PN_NIL || code == PN_FALSE || code == PN_NONE) {
    // filename[len + sizeof(PN_EXT_SRC) - 1] = '\0';
    snprintf(filename, len + 8, "%s" PN_EXT_SRC, module);
    code = load_or_compile(P, filename);
  }
  if (PN_IS_ERROR(code)) {
    return code;
  }
  if (code != PN_NIL) {
    // return potion_run(P, code);
    PN ret = potion_run(P, code);
    return PN_TEST(ret) ? ret : PN_TRUE;
  }

  snprintf(filename, len + 8, "%s" PN_EXT_DYLIB, module);
  code = load_dylib(P, module, filename/* , 0 */);
  if (PN_IS_ERROR(code)) {
    return code;
  }
  if (code != PN_NIL) {
    return code;
  }

  return ERROR("failed to load lib <%s>", module);
}

// 获取模块，按需加载; key: 模块名
static PN get_module(Potion *P, PN key) {
  int ret;
  PN module;
  unsigned k = kh_get(PN, MODS, key);
  if (k != kh_end(MODS)) return kh_val(PN, MODS, k);

  k = kh_put(PN, MODS, key, &ret);
  PN_QUICK_FWD(struct PNTable *, MODS);
  PN_TOUCH(MODS);

  kh_val(PN, MODS, k) = PN_TRUE;
  PN_TOUCH(MODS);

  module = load_module(P, PN_STR_PTR(key), PN_STR_LEN(key));
  if (module == PN_FALSE || PN_IS_ERROR(module))
    kh_del(PN, MODS, k);
  else
    kh_val(PN, MODS, k) = module;
  PN_TOUCH(MODS);

  return module;
}

// 用名字（作为消息接收者）加载模块，返回模块结果 ｜ Error ｜ nil 。
PN_MSG(potion_module_load) {
  // return load_module(P, PN_STR_PTR(self), PN_STR_LEN(self));
  return get_module(P, self);
}

// 用名字（作为参数）加载模块，返回模块结果 ｜ Error ｜ nil 。
PN_MSG(potion_load_module, PN module) {
  // return load_module(P, PN_STR_PTR(module), PN_STR_LEN(module));
  return get_module(P, module);
}

// 列举加载的模块，返回 tuple 。
PN_MSG(potion_module_list) {
  PN ms = PN_TUP0();
  KH_EACH_ASC(PN, MODS, {
    PN_PUSH(ms, kh_key(PN, t, k));
  });
  return ms;
}

// 初始化 VM 的已加载模块表为空表。
void potion_mod_hash_init(Potion *P) {
  // MODS = PN_CALLOC(PN_TTABLE, struct PNTable);
  MODS = (struct PNTable *)potion_table_empty(P);
}

// 注册模块加载相关的消息。
void potion_module_init(Potion *P) {
  // 'preluge' load 可成功。
  // PN_METHOD(P->lobby, "load", potion_module_load,  0);
  PN_METHOD(PN_VTABLE(PN_TSTRING), "load", potion_module_load, 0);
  // load 'preluge'
  PN_METHOD(P->lobby, "load",    potion_load_module, "module=S");
  PN_METHOD(P->lobby, "modules", potion_module_list, 0);
}